package com.apobates.jforum.grief.aurl.utils.defend;

import com.apobates.jforum.grief.aurl.spring.CollectionUtils;
import com.apobates.jforum.grief.aurl.spring.StringUtils;
import com.apobates.jforum.grief.aurl.utils.DateTimeUtils;
import com.apobates.jforum.grief.aurl.utils.Tuple;

import java.io.IOException;
import java.math.BigDecimal;
import java.text.MessageFormat;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import java.util.*;
import java.util.function.Predicate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Predicates {
    private final static Pattern numericMode = Pattern.compile("[0-9]+[\\.]?[0-9]*");
    private static final Pattern alphaMode =  Pattern.compile("^[a-zA-Z]*$");
    // 默认消息的外部化
    /*
    *
        futureStr="目标不是未来时间"
        future=目标不是未来时间
        pastStr=目标不是过去时间
        past=目标不是过去时间
        decimalFull="目标不满足金额数字定义, 长度:{0}, 小数位:{1}"
        decimalScale="目标不满足金额数字定义,  小数位:{0}"
        decimal=目标不是金额数字
        numeric=目标不是数字
        alpha=目标不是字母组合
        zero=目标不是0
        range=目标值不在指定的范围之内, 开始:{0}, 结束:{1}
        positive=目标不是大于0的正整数
        unsigned=目标不是无符号正整数
        matchTo=目标不匹配指定的模式, 模式:{0}
        equalsTo=两者不相等, 期望值:{0}
        contains=不包含目标元素, 查找值:{0}
        notEmpty=不存在可用的元素
        notNull=目标参数不可用
        basic=执行中断
    * */
    private final static Map<String,String> message_mapper;
    static {
        message_mapper = new HashMap<>();
        try {
            String pn = String.format("defend.assert.%s.properties", Locale.getDefault().getCountry().toLowerCase());
            for (Map.Entry<Object, Object> entry : CollectionUtils.loadMetaInfoProperties(pn).entrySet()) {
                message_mapper.put(String.valueOf(entry.getKey()), String.valueOf(entry.getValue()));
            }
        }catch (IOException e) {
            message_mapper.putAll(getDefaultMessage());
        }
    }
    /**
     * 谓词函数校验规则
     * @param predicate 谓词函数
     * @param message 失败消息
     * @return
     * @param <T>
     */
    public static <T> AssertPredicate<T> predicate(String label, Predicate<T> predicate, String message){
        Objects.requireNonNull(predicate);
        final String msg = getElse(message, "{basic}");
        return AssertPredicateFunction.of(label, msg, predicate);
    }

    /**
     * 非Null判断条件, 非null表示成功, null表示失败。
     * @param message 失败的消息
     * @return
     * @param <T>
     */
    public static <T> AssertPredicate<T> notNull(String message){
        final String msg = getElse(message, "{notNull}");
        return predicate("notNull", (value)-> null!=value, msg);
    }

    /**
     * 非空判断条件, 非空表示成功, 空表示失败。
     * 适用于: Collection实现, Map实现, String, Optional
     * @param message 失败的消息
     * @return
     * @param <T>
     */
    public static <T> AssertPredicate<T> notEmpty(String message){
        final String msg = getElse(message, "{notEmpty}");
        Predicate<T> predicate = (value)->{
            if (value instanceof Collection set) {
                return !set.isEmpty();
            }
            if(value instanceof Map map){
                return !map.isEmpty();
            }
            if(value instanceof String str){
                return !str.isEmpty();
            }
            if(value instanceof Optional opt){
                return !opt.isEmpty();
            }
            return true;
        };
        return predicate("notEmpty", predicate, msg);
    }

    /**
     * 包含判断条件, 包含表示成功, 不包含表示失败。
     * 注意: 若T表示Collection或Map时相当于containsAll
     * 适用于: Collection实现, Map实现, String
     * @param message 失败的消息
     * @param element 判断的元素
     * @return
     * @param <T>
     */
    public static <T> AssertPredicate<T> contains(String message, T element){
        Objects.requireNonNull(element);
        Object[] seekElements = convertArgs(element);
        final String msg = getElse(message, "{contains}", seekElements);
        Predicate<T> predicate = (value)->{
            if (value instanceof Collection set) {
                return set.containsAll((Collection<?>) element);
            }
            if (value instanceof Map map) {
                Map _em = (Map)element;
                return map.keySet().containsAll(_em.keySet());
            }
            if(value instanceof String str){
                return str.contains(String.valueOf(element));
            }
            return true;
        };
        return predicate("contains", predicate, msg);
    }

    private static <T> Object[] convertArgs(T element){
        if (element instanceof Collection set) {
            return set.stream().map(Object::toString).toArray();
        }
        if (element instanceof Map map) {
            return map.keySet().stream().map(Object::toString).toArray();
        }
        if(element instanceof String str){
            return new Object[]{str};
        }
        return new Object[]{"-"};
    }

    /**
     * 等于判断条件, 等于表示成功, 不等于表示失败。
     * 适用于: Boolean, Byte, Character, String, Number, Enum
     * @param message 失败的消息
     * @param target 判断的目标
     * @return
     * @param <T>
     */
    public static <T> AssertPredicate<T> equalsTo(String message, T target){
        Objects.requireNonNull(target);
        final String msg = getElse(message,"{equalsTo}", new Object[]{target});
        Predicate<T> predicate = (value)->{
            if (value instanceof Boolean bolv) {
                return bolv.compareTo((Boolean)target) == 0;
            }
            if (value instanceof Byte bytes) {
                return bytes.compareTo((Byte)target) == 0;
            }
            if (value instanceof Character chars) {
                return chars.compareTo((Character)target) == 0;
            }
            if (value instanceof Number num) {
                return num.hashCode() == target.hashCode();
            }
            if(value instanceof String str){
                return str.equals(String.valueOf(target));
            }
            if (value instanceof Enum enums) {
                return enums.compareTo((Enum)target) == 0;
            }
            return true;
        };
        return predicate("equalsTo", predicate, msg);
    }

    /**
     * 正则表达式匹配判断条件, 匹配表示成功, 不匹配表示失败。
     * 适用于: String
     * @param message 失败的消息
     * @param pattern 正则表达式
     * @return
     * @param <T>
     */
    public static <T> AssertPredicate<T> matchTo(String message, Pattern pattern){
        Objects.requireNonNull(pattern);
        final String msg = getElse(message, "{matchTo}", new String[]{pattern.pattern()});
        Predicate<T> predicate = (value)-> {
            if (value instanceof String str) {
                Matcher m = pattern.matcher(str);
                return m.matches();
            }
            return true;
        };
        return predicate("matchTo", predicate, msg);
    }

    /**
     * 无符号正整数判断条件, 是表示成功, 不是表示失败。
     * 适用于: String, Number
     * @param message 失败的消息
     * @return
     * @param <T>
     */
    public static <T> AssertPredicate<T> unsigned(String message){
        final String msg = getElse(message,"{unsigned}");
        Predicate<T> predicate = (value)-> {
            if (value instanceof String str) {
                return StringUtils.isPositive(str, (num)->num >= 0);
            }
            if (value instanceof Number num) {
                return num.longValue() >= 0;
            }
            return true;
        };
        return predicate("unsigned", predicate, msg);
    }

    /**
     * 大于0的正整数判断条件, 是表示成功, 不是表示失败。
     * 适用于: String, Number
     * @param message 失败的消息
     * @return
     * @param <T>
     */
    public static <T> AssertPredicate<T> positive(String message){
        final String msg = getElse(message,"{positive}");
        Predicate<T> predicate = (value)-> {
            if (value instanceof String str) {
                return StringUtils.isPositive(str, (num)->num > 0);
            }
            if (value instanceof Number num) {
                return num.longValue() > 0;
            }
            return true;
        };
        return predicate("positive", predicate, msg);
    }

    /**
     * 范围值的判断, 在范围中表示成功, 不在范围中表示失败。
     * 注意: 只适用于值是线性连续的. 例: 数字(0, 1, 2, ..., 100)或字母(a, b, c, ..., z)
     * 适用于: String, Character, Integer, Long
     * @param message 失败的消息
     * @param start 开始值，在范围内
     * @param finish 结束值，在范围内
     * @return
     * @param <T>
     */
    public static <T> AssertPredicate<T> range(String message, T start, T finish){
        final String msg = getElse(message,"{range}", new Object[]{start, finish});
        Predicate<T> predicate = (value)-> {
            if (value instanceof String str) {
                String sis = (String)start;
                String fif = (String)finish;
                return sis.compareToIgnoreCase(str)>=0 && fif.compareToIgnoreCase(str)<=0;
            }
            if (value instanceof Character chaVal) {
                Character si = (Character)start;
                Character fi =  (Character) finish;
                return si.compareTo(chaVal)>=0 && fi.compareTo(chaVal)<=0;
            }
            if (value instanceof Integer intRange) {
                Integer si = (Integer)start;
                Integer fi =  (Integer) finish;
                return si.compareTo(intRange)>=0 && fi.compareTo(intRange)<=0;
            }
            if (value instanceof Long longRange) {
                Long si = (Long)start;
                Long fi =  (Long) finish;
                return si.compareTo(longRange)>=0 && fi.compareTo(longRange)<=0;
            }
            return true;
        };
        return predicate("range", predicate, msg);
    }

    /**
     * 等于0判断。 等于表示成功, 不等于表示失败。
     * 适用于: String, Number
     * @param message 失败的消息
     * @return
     * @param <T>
     */
    public static <T> AssertPredicate<T> zero(String message){
        final String msg = getElse(message,"{zero}");
        Predicate<T> predicate = (value)-> {
            if (value instanceof String str) {
                return str.equals("0");
            }
            if (value instanceof Number num) {
                return num.longValue() == 0;
            }
            return true;
        };
        return predicate("zero", predicate, msg);
    }

    /**
     * 是否是数字。满足表示成功, 不满足表示失败。
     * 注意: 基于数字正则表达式
     * 适用于: String
     * @param message 失败的消息
     * @return
     * @param <T>
     */
    public static <T> AssertPredicate<T> numeric(String message){
        final String msg = getElse(message,"{numeric}");
        Predicate<T> predicate = (value)-> {
            if (value instanceof String str) {
                Matcher m = numericMode.matcher(str);
                return m.matches();
            }
            return true;
        };
        return predicate("numeric", predicate, msg);
    }
    /**
     * 是否是字母。满足表示成功, 不满足表示失败。
     * 注意: 基于数字正则表达式
     * 适用于: String
     * @param message 失败的消息
     * @return
     * @param <T>
     */
    public static <T> AssertPredicate<T> alpha(String message){
        final String msg = getElse(message,"{alpha}");
        Predicate<T> predicate = (value)-> {
            if (value instanceof String str) {
                return isAlpha(str);
            }
            return true;
        };
        return predicate("alpha", predicate, msg);
    }
    /**
     * 金额判断。 满足数字表示成功, 不满足表示失败。
     * 注意: 基于与BigDecimal的转换是否成功
     * 适用于: String, Float, Double, BigDecimal
     * @param message 失败的消息
     * @return
     * @param <T>
     */
    public static <T> AssertPredicate<T> decimal(String message){
        final String msg = getElse(message,"{decimal}");
        Predicate<T> predicate = (value)-> {
            if (value instanceof String str) {
                Tuple<Boolean, Integer, Integer> _strDecimal = parseDecimal(str);
                return _strDecimal.left();
            }
            if (value instanceof Float floatVal) {
                Tuple<Boolean, Integer, Integer> _strDecimal = parseDecimal(floatVal);
                return _strDecimal.left();
            }
            if (value instanceof Double doubleVal) {
                Tuple<Boolean, Integer, Integer> _strDecimal = parseDecimal(doubleVal);
                return _strDecimal.left();
            }
            if (value instanceof BigDecimal decimalVal) {
                return null!=decimalVal;
            }
            return true;
        };
        return predicate("decimal", predicate, msg);
    }

    /**
     * 金额判断。 满足数字且小数位符合表示成功, 不满足表示失败。
     * 注意: 基于与BigDecimal的转换是否成功
     * 适用于: String, Float, Double, BigDecimal
     * @param message 失败的消息
     * @param scale 范围/小数位长度. 要求小数位相等
     * @return
     * @param <T>
     */
    public static <T> AssertPredicate<T> decimalScale(String message, int scale){
        final String msg = getElse(message, "{decimalScale}", new Integer[]{scale});
        Predicate<T> predicate = (value)-> {
            if (value instanceof String str) {
                Tuple<Boolean, Integer, Integer> _strDecimal = parseDecimal(str);
                return _strDecimal.left() && _strDecimal.right().intValue() == scale;
            }
            if (value instanceof Float floatVal) {
                Tuple<Boolean, Integer, Integer> _strDecimal = parseDecimal(floatVal);
                return _strDecimal.left() && _strDecimal.right().intValue() == scale;
            }
            if (value instanceof Double doubleVal) {
                Tuple<Boolean, Integer, Integer> _strDecimal = parseDecimal(doubleVal);
                return _strDecimal.left() && _strDecimal.right().intValue() == scale;
            }
            if (value instanceof BigDecimal decimalVal) {
                return null!=decimalVal && decimalVal.scale() == scale;
            }
            return true;
        };
        return predicate("decimalScale", predicate, msg);
    }

    /**
     * 金额判断。满足数字且小数位符合表示成功, 不满足表示失败。
     * 注意: 基于与BigDecimal的转换是否成功
     * 适用于: String, Float, Double, BigDecimal
     * @param message 失败的消息
     * @param precision 精度/数字总长度. 要求数字位大于等于
     * @param scale 范围/小数位长度. 要求小数位相等
     * @return
     * @param <T>
     */
    public static <T> AssertPredicate<T> decimalFull(String message, int precision, int scale){
        final String msg = getElse(message, "{decimalFull}", new Integer[]{precision, scale});
        Predicate<T> predicate = (value)-> {
            if (value instanceof String str) {
                Tuple<Boolean, Integer, Integer> _strDecimal = parseDecimal(str);
                return _strDecimal.left() && _strDecimal.right().intValue() == scale && _strDecimal.middle() >= precision;
            }
            if (value instanceof Float floatVal) {
                Tuple<Boolean, Integer, Integer> _strDecimal = parseDecimal(floatVal);
                return _strDecimal.left() && _strDecimal.right().intValue() == scale && _strDecimal.middle() >= precision;
            }
            if (value instanceof Double doubleVal) {
                Tuple<Boolean, Integer, Integer> _strDecimal = parseDecimal(doubleVal);
                return _strDecimal.left() && _strDecimal.right().intValue() == scale && _strDecimal.middle() >= precision;
            }
            if (value instanceof BigDecimal decimalVal) {
                return null!=decimalVal && decimalVal.scale() == scale && decimalVal.precision() >= precision;
            }
            return true;
        };
        return predicate("decimalFull", predicate, msg);
    }

    /**
     * 过去时间判断。满足时间且小于当前时区时间表示成功, 不满足表示失败。
     * 注意: 若T是字符串时要求符合: yyyy-MM-dd HH:mm:ss 或 yyyy-MM-dd 格式
     * 适用于: String, LocalDate, LocalDateTime, ZonedDateTime, Instant
     * @param message 失败的消息
     * @return
     * @param <T>
     */
    public static <T> AssertPredicate<T> past(String message){
        final String msg = getElse(message,"{past}");
        Predicate<T> predicate = (value)-> {
            if (value instanceof String str) {
                // 有: 日期+时间, 有: 日期
                LocalDateTime dateTime = DateTimeUtils.of(str, "yyyy-MM-dd HH:mm:ss").orElse(DateTimeUtils.parse(str, "yyyy-MM-dd"));
                return DateTimeUtils.isPast(dateTime);
            }
            if (value instanceof LocalDate ld) {
                return DateTimeUtils.isPast(ld);
            }
            if (value instanceof LocalDateTime ldt) {
                return DateTimeUtils.isPast(ldt);
            }
            if (value instanceof ZonedDateTime zdt) {
                return DateTimeUtils.isPast(zdt);
            }
            if (value instanceof Instant instant) {
                return DateTimeUtils.isPast(instant);
            }
            return true;
        };
        return predicate("past", predicate, msg);
    }

    /**
     * 过去时间判断。满足时间且小于当前时区时间表示成功, 不满足表示失败。
     * 适用于: String
     * @param message 失败的消息
     * @param format 日期的格式
     * @return
     * @param <T>
     */
    public static <T> AssertPredicate<T> pastStr(String message, String format){
        final String msg = getElse(message, "{pastStr}", new String[]{format});
        Predicate<T> predicate = (value)-> {
            if (value instanceof String str) {
                // 有: 日期+时间, 有: 日期
                Optional<LocalDateTime> dateTime = DateTimeUtils.of(str, format);
                return dateTime.isPresent() && DateTimeUtils.isPast(dateTime.get());
            }
            return true;
        };
        return predicate("pastStr", predicate, msg);
    }


    /**
     * 未来时间判断。满足时间且大于当前时区时间表示成功, 不满足表示失败。
     * 注意: 若T是字符串时要求符合: yyyy-MM-dd HH:mm:ss 或 yyyy-MM-dd 格式
     * 适用于: String, LocalDate, LocalDateTime, ZonedDateTime, Instant
     * @param message 失败的消息
     * @return
     * @param <T>
     */
    public static <T> AssertPredicate<T> future(String message){
        final String msg = getElse(message,"{future}");
        Predicate<T> predicate = (value)-> {
            if (value instanceof String str) {
                // 有: 日期+时间, 有: 日期
                LocalDateTime dateTime = DateTimeUtils.of(str, "yyyy-MM-dd HH:mm:ss").orElse(DateTimeUtils.parse(str, "yyyy-MM-dd"));
                return DateTimeUtils.isFuture(dateTime);
            }
            if (value instanceof LocalDate ld) {
                return DateTimeUtils.isFuture(ld);
            }
            if (value instanceof LocalDateTime ldt) {
                return DateTimeUtils.isFuture(ldt);
            }
            if (value instanceof ZonedDateTime zdt) {
                return DateTimeUtils.isFuture(zdt);
            }
            if (value instanceof Instant instant) {
                return DateTimeUtils.isFuture(instant);
            }
            return true;
        };
        return predicate("future", predicate, msg);
    }

    /**
     * 未来时间判断。满足时间且大于当前时区时间表示成功, 不满足表示失败。
     * 适用于: String
     * @param message 失败的消息
     * @param format 日期的格式
     * @return
     * @param <T>
     */
    public static <T> AssertPredicate<T> futureStr(String message, String format){
        final String msg = getElse(message, "{futureStr}", new String[]{format});
        Predicate<T> predicate = (value)-> {
            if (value instanceof String str) {
                // 有: 日期+时间, 有: 日期
                Optional<LocalDateTime> dateTime = DateTimeUtils.of(str, format);
                return dateTime.isPresent() && DateTimeUtils.isFuture(dateTime.get());
            }
            return true;
        };
        return predicate("futureStr", predicate, msg);
    }

    /**
     * 使用Predicate函数实现AssertCondition
     * @param <T> 目标类型
     */
    private static class AssertPredicateFunction<T> implements AssertPredicate<T> {
        private final Predicate<T> condition;
        private final String message;
        private final String label;
        private AssertPredicateFunction(String label, String message, Predicate<T> condition) {
            this.condition = condition;
            this.message = message;
            this.label = label;
        }

        public static <T> AssertPredicateFunction<T> of(String label, String message, Predicate<T> condition){
            Objects.requireNonNull(message);
            Objects.requireNonNull(condition);
            String preLabel = getElse(label, "basic");
            return new AssertPredicateFunction<>(preLabel, message, condition);
        }

        @Override
        public boolean predicate(T target) {
            return condition.test(target);
        }

        @Override
        public String getError(){
            return this.message;
        }

        @Override
        public String getLabel() {
            return this.label;
        }
    }

    private static String getElse(String value, String defaultValue, Object ... args){
        return (null == value) ? getErrorMessage(defaultValue, args) : getErrorMessage(value, args);
    }
    private static String getErrorMessage(String mask, Object... args){
        if(null == mask){
            return message_mapper.get("basic");
        }
        // 是否是占位符:xxx(格式:{xxx}且xxx是字符组合无空格)
        if(mask.startsWith("{") && mask.endsWith("}")) {
            // message_mapper是否有定义
            String findPK = mask.substring(1, mask.length() -1);
            if(isAlpha(findPK) && message_mapper.containsKey(findPK)) {
                // 是否需要格式化
                // MessageFormat.format(message_mapper.get("contains"), seekElements)
                String outmessage = message_mapper.get(findPK);
                if(args.length>0){
                    return MessageFormat.format(outmessage, args);
                }
                return outmessage;
            }
            return message_mapper.get("basic");
        }
        // 若无直接输出
        return mask;

    }

    // 是否是字符组合
    private static boolean isAlpha(String value){
        return alphaMode.matcher(value).find();
    }
    // 默认的中文消息模板
    private static Map<String,String> getDefaultMessage(){
        Map<String,String> data = new HashMap<>();
        message_mapper.put("futureStr", "目标不是未来时间");
        message_mapper.put("future", "目标不是未来时间");
        message_mapper.put("pastStr", "目标不是过去时间");
        message_mapper.put("past", "目标不是过去时间");
        message_mapper.put("decimalFull", "目标不满足金额数字定义, 长度:{0}, 小数位:{1}");
        message_mapper.put("decimalScale", "目标不满足金额数字定义,  小数位:{0}");
        message_mapper.put("decimal", "目标不是金额数字");
        message_mapper.put("numeric", "目标不是数字");
        message_mapper.put("alpha", "目标不是字母组合");
        message_mapper.put("zero", "目标不是0");
        message_mapper.put("range", "目标值不在指定的范围之内, 开始:{0}, 结束:{1}");
        message_mapper.put("positive", "目标不是大于0的正整数");
        message_mapper.put("unsigned", "目标不是无符号正整数");
        message_mapper.put("matchTo", "目标不匹配指定的模式, 模式:{0}");
        message_mapper.put("equalsTo", "两者不相等, 期望值:{0}");
        message_mapper.put("contains", "不包含目标元素, 查找值:{0}");
        message_mapper.put("notEmpty", "不存在可用的元素");
        message_mapper.put("notNull", "目标参数不可用");
        message_mapper.put("basic", "执行中断");
        return data;
    }
    // 加载meta-info下的
    private static Tuple<Boolean, Integer, Integer> parseDecimal(String value){
        Integer scale = 0;
        Integer precision = 0;
        Boolean isDecimal = false;
        try{
            BigDecimal bd = null!=value?new BigDecimal(value):BigDecimal.ZERO;
            isDecimal = bd.compareTo(BigDecimal.ZERO) == 1;
            scale = bd.scale();
            precision = bd.precision();
        }catch (Exception e){

        }
        return Tuple.of(isDecimal, precision, scale);
    }

    private static Tuple<Boolean, Integer, Integer> parseDecimal(Float value){
        Integer scale = 0;
        Integer precision = 0;
        Boolean isDecimal = false;
        try{
            BigDecimal bd = null!=value?new BigDecimal(value.floatValue()):BigDecimal.ZERO;
            isDecimal = bd.compareTo(BigDecimal.ZERO) == 1;
            scale = bd.scale();
            precision = bd.precision();
        }catch (Exception e){

        }
        return Tuple.of(isDecimal, precision, scale);
    }

    private static Tuple<Boolean, Integer, Integer> parseDecimal(Double value){
        Integer scale = 0;
        Integer precision = 0;
        Boolean isDecimal = false;
        try{
            BigDecimal bd = null!=value?new BigDecimal(value.doubleValue()):BigDecimal.ZERO;
            isDecimal = bd.compareTo(BigDecimal.ZERO) == 1;
            scale = bd.scale();
            precision = bd.precision();
        }catch (Exception e){

        }
        return Tuple.of(isDecimal, precision, scale);
    }
}
